// SPDX-License-Identifier: GPL-2.0+
/*
 * Most of this source has been derived from the Linux USB
 * project:
 * (C) Copyright Linus Torvalds 1999
 * (C) Copyright Johannes Erdfelt 1999-2001
 * (C) Copyright Andreas Gal 1999
 * (C) Copyright Gregory P. Smith 1999
 * (C) Copyright Deti Fliegl 1999 (new USB architecture)
 * (C) Copyright Randy Dunlap 2000
 * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id)
 * (C) Copyright Yggdrasil Computing, Inc. 2000
 *     (usb_device_id matching changes by Adam J. Richter)
 *
 * Adapted for U-Boot:
 * (C) Copyright 2001 Denis Peter, MPL AG Switzerland
 */

/****************************************************************************
 * HUB "Driver"
 * Probes device for being a hub and configurate it
 */

#include <common.h>
#include <command.h>
#include <dm.h>
#include <env.h>
#include <errno.h>
#include <malloc.h>
#include <memalign.h>
#include <asm/processor.h>
#include <asm/unaligned.h>
#include <linux/ctype.h>
#include <linux/list.h>
#include <asm/byteorder.h>
#ifdef CONFIG_SANDBOX
#include <asm/state.h>
#endif
#include <asm/unaligned.h>

#include <usb.h>

#define USB_BUFSIZ	512

#define HUB_SHORT_RESET_TIME	20
#define HUB_LONG_RESET_TIME	200

#define PORT_OVERCURRENT_MAX_SCAN_COUNT		3

struct usb_device_scan {
	struct usb_device *dev;		/* USB hub device to scan */
	struct usb_hub_device *hub;	/* USB hub struct */
	int port;			/* USB port to scan */
	struct list_head list;
};

static LIST_HEAD(usb_scan_list);

__weak void usb_hub_reset_devices(struct usb_hub_device *hub, int port)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	return;
}

static inline bool usb_hub_is_superspeed(struct usb_device *hdev)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	return hdev->descriptor.bDeviceProtocol == 3;
}

#if CONFIG_IS_ENABLED(DM_USB)
bool usb_hub_is_root_hub(struct udevice *hub)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	if (device_get_uclass_id(hub->parent) != UCLASS_USB_HUB)
		return true;

	return false;
}

static int usb_set_hub_depth(struct usb_device *dev, int depth)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	if (depth < 0 || depth > 4)
		return -EINVAL;

	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
		USB_REQ_SET_HUB_DEPTH, USB_DIR_OUT | USB_RT_HUB,
		depth, 0, NULL, 0, USB_CNTL_TIMEOUT);
}
#endif

static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	unsigned short dtype = USB_DT_HUB;

	if (usb_hub_is_superspeed(dev))
		dtype = USB_DT_SS_HUB;

	return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
		USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
		dtype << 8, 0, data, size, USB_CNTL_TIMEOUT);
}

static int usb_clear_port_feature(struct usb_device *dev, int port, int feature)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
				USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature,
				port, NULL, 0, USB_CNTL_TIMEOUT);
}

static int usb_set_port_feature(struct usb_device *dev, int port, int feature)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
				USB_REQ_SET_FEATURE, USB_RT_PORT, feature,
				port, NULL, 0, USB_CNTL_TIMEOUT);
}

static int usb_get_hub_status(struct usb_device *dev, void *data)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0,
			data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT);
}

int usb_get_port_status(struct usb_device *dev, int port, void *data)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	int ret;

	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
			USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port,
			data, sizeof(struct usb_port_status), USB_CNTL_TIMEOUT);

#if CONFIG_IS_ENABLED(DM_USB)
	if (ret < 0)
		return ret;

	/*
	 * Translate the USB 3.0 hub port status field into the old version
	 * that U-Boot understands. Do this only when the hub is not root hub.
	 * For root hub, the port status field has already been translated
	 * in the host controller driver (see xhci_submit_root() in xhci.c).
	 *
	 * Note: this only supports driver model.
	 */

	if (!usb_hub_is_root_hub(dev->dev) && usb_hub_is_superspeed(dev)) {
		struct usb_port_status *status = (struct usb_port_status *)data;
		u16 tmp = (status->wPortStatus) & USB_SS_PORT_STAT_MASK;

		if (status->wPortStatus & USB_SS_PORT_STAT_POWER)
			tmp |= USB_PORT_STAT_POWER;
		if ((status->wPortStatus & USB_SS_PORT_STAT_SPEED) ==
		    USB_SS_PORT_STAT_SPEED_5GBPS)
			tmp |= USB_PORT_STAT_SUPER_SPEED;

		status->wPortStatus = tmp;
	}
#endif

	return ret;
}


void usb_hub_power_on(struct usb_hub_device *hub)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	int i;
	struct usb_device *dev;

	i=0;
	{
		extern void gm_hub_port_power_on(struct usb_device *dev, int port);
		gm_hub_port_power_on(dev, i+1);
	}

#if 0
	/*
	 * Do a minimum delay of the larger value of 100ms or pgood_delay
	 * so that the power can stablize before the devices are queried
	 */
	hub->query_delay = get_timer(0) + max(100, (int)pgood_delay);

	/*
	 * Record the power-on timeout here. The max. delay (timeout)
	 * will be done based on this value in the USB port loop in
	 * usb_hub_configure() later.
	 */
	hub->connect_timeout = hub->query_delay + 1000;
	debug("devnum=%d poweron: query_delay=%d connect_timeout=%d\n",
	      dev->devnum, max(100, (int)pgood_delay),
	      max(100, (int)pgood_delay) + 1000);
#endif
}

static struct usb_hub_device hub_dev[USB_MAX_HUB];
#if 1//!CONFIG_IS_ENABLED(DM_USB)
static struct usb_hub_device hub_dev[USB_MAX_HUB];
static int usb_hub_index;

void usb_hub_reset(void)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	usb_hub_index = 0;

	/* Zero out global hub_dev in case its re-used again */
	memset(hub_dev, 0, sizeof(hub_dev));
}

static struct usb_hub_device *usb_hub_allocate(void)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	if (usb_hub_index < USB_MAX_HUB)
		return &hub_dev[usb_hub_index++];

	printf("ERROR: USB_MAX_HUB (%d) reached\n", USB_MAX_HUB);
	return NULL;
}
#endif

#define MAX_TRIES 5

static inline const char *portspeed(int portstatus)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	switch (portstatus & USB_PORT_STAT_SPEED_MASK) {
	case USB_PORT_STAT_SUPER_SPEED:
		return "5 Gb/s";
	case USB_PORT_STAT_HIGH_SPEED:
		return "480 Mb/s";
	case USB_PORT_STAT_LOW_SPEED:
		return "1.5 Mb/s";
	default:
		return "12 Mb/s";
	}
}

/**
 * usb_hub_port_reset() - reset a port given its usb_device pointer
 *
 * Reset a hub port and see if a device is present on that port, providing
 * sufficient time for it to show itself. The port status is returned.
 *
 * @dev:	USB device to reset
 * @port:	Port number to reset (note ports are numbered from 0 here)
 * @portstat:	Returns port status
 */
static int usb_hub_port_reset(struct usb_device *dev, int port,
			      unsigned short *portstat)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	int tries;
	ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
	unsigned short portstatus;
	int delay = HUB_SHORT_RESET_TIME; /* start with short reset delay */

	for (tries = 0; tries < MAX_TRIES; tries++) {
		gm_port_feat_reset(dev, port+1);

		mdelay(delay);

		gm_get_port_status(dev, port+1, portsts);

		portstatus = le16_to_cpu(portsts->wPortStatus);

		if (portstatus & USB_PORT_STAT_ENABLE)
			break;

		/* Switch to long reset delay for the next round */
		delay = HUB_LONG_RESET_TIME;
	}

	gm_port_feat_c_reset(dev, port+1);
	return 0;
}


struct usb_configuration_descriptor {
	u8 bLength;
	u8 bDescriptorType;	/* 0x2 */
	u16 wTotalLength;
	u8 bNumInterfaces;
	u8 bConfigurationValue;
	u8 iConfiguration;
	u8 bmAttributes;
	u8 bMaxPower;
} __attribute__ ((packed));

int usb_hub_port_connect_change(struct usb_device *dev, int port)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	struct usb_configuration_descriptor *config_desc = NULL;
	struct usb_interface_descriptor *interface_desc = NULL;
	struct usb_endpoint_descriptor *ep_desc[2] = {0};
	ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, 64);

	/* Clear the connection change status */
	gm_clear_port_feature(dev, port+1);

	/* Reset the port */
	usb_hub_port_reset(dev, port, NULL);

	gm_ehci_get_dev_desc(dev, 0);
	gm_ehci_set_device_address(dev, 2);
	gm_ehci_get_dev_desc(dev, 2);
	gm_ehci_get_config_desc(dev, 2, buffer);

	config_desc = (struct usb_configuration_descriptor *)buffer;
	interface_desc = (struct usb_interface_descriptor *)(buffer + config_desc->bLength);

	/* 我知道这个u盘只有两个端点。。。 */
	ep_desc[0] = (struct usb_endpoint_descriptor *)(buffer + config_desc->bLength + interface_desc->bLength);
	ep_desc[1] = (struct usb_endpoint_descriptor *)(buffer + config_desc->bLength + interface_desc->bLength +ep_desc[0]->bLength);

	print_config_desc(config_desc);
	print_interface_desc(interface_desc);
	print_endpoint_desc(ep_desc[0]);
	print_endpoint_desc(ep_desc[1]);

	gm_ehci_set_config(2, config_desc->bConfigurationValue);
	udisk_probe(ep_desc);
	while(1);

	return 0;
}

static int usb_scan_port(struct usb_device_scan *usb_scan)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1);
	unsigned short portstatus;
	unsigned short portchange;
	struct usb_device *dev;
	struct usb_hub_device *hub;
	int ret = 0;
	int i;

#if 0
	dev = usb_scan->dev;
	hub = usb_scan->hub;
	i = usb_scan->port;

	gm_get_port_status(dev, i+1, portsts);

	portstatus = le16_to_cpu(portsts->wPortStatus);
	portchange = le16_to_cpu(portsts->wPortChange);
#endif

	pr_err("%s:in %d.\n",__func__,__LINE__); //here
	usb_hub_port_connect_change(dev, 0);

	return 0;
}

static int usb_device_list_scan(void)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	struct usb_device_scan *usb_scan;
	struct usb_device_scan *tmp;
	static int running;
	int ret = 0;

	/* Only run this loop once for each controller */
	if (running)
		return 0;

	running = 1;

	while (1) {

		list_for_each_entry_safe(usb_scan, tmp, &usb_scan_list, list) {
			int ret;

			/* Scan this port */
			ret = usb_scan_port(usb_scan);
			if (ret)
				goto out;
		}
	}

out:
	/*
	 * This USB controller has finished scanning all its connected
	 * USB devices. Set "running" back to 0, so that other USB controllers
	 * will scan their devices too.
	 */
	running = 0;

	return ret;
}

static struct usb_hub_device *usb_get_hub_device(struct usb_device *dev)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	struct usb_hub_device *hub;

#if 1 //!CONFIG_IS_ENABLED(DM_USB)
	/* "allocate" Hub device */
	hub = usb_hub_allocate();
#else
	hub = dev_get_uclass_priv(dev->dev);
#endif

	return hub;
}

int usb_hub_configure(struct usb_device *dev)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	int i, length;
	ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, USB_BUFSIZ);
	unsigned char *bitmap;
	short hubCharacteristics;
	struct usb_hub_descriptor *descriptor;
	struct usb_hub_device *hub;
	struct usb_hub_status *hubsts;
	int ret;
	struct usb_device_scan *usb_scan;

#if 0
	hub = usb_get_hub_device(dev);
	if (hub == NULL)
		return -ENOMEM;
	hub->pusb_dev = dev;

	gm_get_hub_device_desc(buffer);

	descriptor = (struct usb_hub_descriptor *)buffer;

	length = min_t(int, descriptor->bLength,
		       sizeof(struct usb_hub_descriptor));

	memcpy((unsigned char *)&hub->desc, buffer, length);
	/* adjust 16bit values */
	put_unaligned(le16_to_cpu(get_unaligned(
			&descriptor->wHubCharacteristics)),
			&hub->desc.wHubCharacteristics);
	/* set the bitmap */
	bitmap = (unsigned char *)&hub->desc.u.hs.DeviceRemovable[0];
	/* devices not removable by default */
	memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8);
	bitmap = (unsigned char *)&hub->desc.u.hs.PortPowerCtrlMask[0];
	memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */

	i=0;
	hub->desc.u.hs.DeviceRemovable[i] =
		descriptor->u.hs.DeviceRemovable[i];

	hub->desc.u.hs.PortPowerCtrlMask[i] =
		descriptor->u.hs.PortPowerCtrlMask[i];


	hubCharacteristics = get_unaligned(&hub->desc.wHubCharacteristics);

	hub->tt.think_time = 666;

	gm_get_hub_status(buffer);


	hubsts = (struct usb_hub_status *)buffer;

	hub->hub_depth = -1;
#endif
	usb_hub_power_on(hub);

#if 0
	i=0;
	{
		usb_scan = calloc(1, sizeof(*usb_scan));
		if (!usb_scan) {
			printf("Can't allocate memory for USB device!\n");
			return -ENOMEM;
		}
		usb_scan->dev = dev;
		usb_scan->hub = hub;
		usb_scan->port = i;
		//list_add_tail(&usb_scan->list, &usb_scan_list);
	}
#endif

	usb_hub_port_connect_change(dev, 0);
	//ret = usb_scan_port(usb_scan);

	return ret;
}

static int usb_hub_check(struct usb_device *dev, int ifnum)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	struct usb_interface *iface;
	struct usb_endpoint_descriptor *ep = NULL;

	iface = &dev->config.if_desc[ifnum];
	/* Is it a hub? */
	if (iface->desc.bInterfaceClass != USB_CLASS_HUB)
		goto err;
	/* Some hubs have a subclass of 1, which AFAICT according to the */
	/*  specs is not defined, but it works */
	if ((iface->desc.bInterfaceSubClass != 0) &&
	    (iface->desc.bInterfaceSubClass != 1))
		goto err;
	/* Multiple endpoints? What kind of mutant ninja-hub is this? */
	if (iface->desc.bNumEndpoints != 1)
		goto err;
	ep = &iface->ep_desc[0];
	/* Output endpoint? Curiousier and curiousier.. */
	if (!(ep->bEndpointAddress & USB_DIR_IN))
		goto err;
	/* If it's not an interrupt endpoint, we'd better punt! */
	if ((ep->bmAttributes & 3) != 3)
		goto err;
	/* We found a hub */
	debug("USB hub found\n");
	return 0;

err:
	debug("USB hub not found: bInterfaceClass=%d, bInterfaceSubClass=%d, bNumEndpoints=%d\n",
	      iface->desc.bInterfaceClass, iface->desc.bInterfaceSubClass,
	      iface->desc.bNumEndpoints);
	if (ep) {
		debug("   bEndpointAddress=%#x, bmAttributes=%d",
		      ep->bEndpointAddress, ep->bmAttributes);
	}

	return -ENOENT;
}

int usb_hub_probe(struct usb_device *dev, int ifnum)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	int ret;

	ret = usb_hub_check(dev, ifnum);
	if (ret)
		return 0;
	ret = usb_hub_configure(dev);
	return ret;
}

#if CONFIG_IS_ENABLED(DM_USB)
int usb_hub_scan(struct udevice *hub)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	struct usb_device *udev = NULL; //dev_get_parent_priv(hub);

	return usb_hub_configure(udev);
}

int usb_hub_post_probe(struct udevice *dev)
{
	pr_err("%s:in %d.\n",__func__,__LINE__);
	debug("%s\n", __func__);
	return usb_hub_scan(dev);
}

static const struct udevice_id usb_hub_ids[] = {
	{ .compatible = "usb-hub" },
	{ }
};

U_BOOT_DRIVER(usb_generic_hub) = {
	.name	= "usb_hub",
	.id	= UCLASS_USB_HUB,
	.of_match = usb_hub_ids,
	.flags	= DM_FLAG_ALLOC_PRIV_DMA,
};

UCLASS_DRIVER(usb_hub) = {
	.id		= UCLASS_USB_HUB,
	.name		= "usb_hub",
	.post_bind	= dm_scan_fdt_dev,
	.post_probe	= usb_hub_post_probe,
	.child_pre_probe	= usb_child_pre_probe,
	.per_child_auto_alloc_size = sizeof(struct usb_device),
	.per_child_platdata_auto_alloc_size = sizeof(struct usb_dev_platdata),
	.per_device_auto_alloc_size = sizeof(struct usb_hub_device),
};

static const struct usb_device_id hub_id_table[] = {
	{
		.match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
		.bDeviceClass = USB_CLASS_HUB
	},
	{ }	/* Terminating entry */
};

U_BOOT_USB_DEVICE(usb_generic_hub, hub_id_table);

#endif
